1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
// (1) next-auth에서 필요한 타입들을 import
import NextAuth, {
NextAuthOptions, // authOptions에 쓸 타입
Session,
User
} from 'next-auth'
import { JWT } from "next-auth/jwt"
import CredentialsProvider from 'next-auth/providers/credentials'
import { verifyExternalCredentials, verifyOtp } from '@/lib/users/verifyOtp'
// 1) 모듈 보강 선언
declare module "next-auth" {
/**
* Session 객체를 확장
*/
interface Session {
user: {
/** 우리가 필요로 하는 user id */
id: string
// 기본적으로 NextAuth가 제공하는 name/email/image 필드
name?: string | null
email?: string | null
image?: string | null
companyId?: number | null
domain?: string | null
}
}
/**
* User 객체를 확장
*/
interface User {
id: string
imageUrl?: string | null
companyId?: number | null
domain?: string | null
// 필요한 필드를 추가로 선언 가능
}
}
// (2) authOptions에 NextAuthOptions 타입 지정
export const authOptions: NextAuthOptions = {
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
email: { label: 'Email', type: 'text' },
code: { label: 'OTP code', type: 'text' },
},
async authorize(credentials, req) {
const { email, code } = credentials ?? {}
// OTP 검증
const user = await verifyOtp(email ?? '', code ?? '')
if (!user) {
return null
}
return {
id: String(user.id ?? email ?? "dts"),
email: user.email,
imageUrl: user.imageUrl ?? null,
name: user.name, // DB에서 가져온 실제 이름
companyId: user.companyId, // DB에서 가져온 실제 이름
domain: user.domain, // DB에서 가져온 실제 이름
}
},
}),
// 새로 추가할 ID/비밀번호 provider
CredentialsProvider({
id: 'credentials-password',
name: 'Username Password',
credentials: {
username: { label: "Username", type: "text" },
password: { label: "Password", type: "password" }
},
async authorize(credentials, req) { // req 매개변수 추가
if (!credentials?.username || !credentials?.password) {
return null;
}
try {
// 여기서 외부 서비스 API를 호출하여 사용자 인증
const user = await verifyExternalCredentials(
credentials.username,
credentials.password
);
if (user) {
return {
id: String(user.id), // id를 string으로 변환
name: user.name,
email: user.email,
// 첫 번째 provider와 동일한 필드 구조 유지
imageUrl: user.imageUrl ?? null,
companyId: user.companyId,
domain: user.domain
};
}
return null;
} catch (error) {
console.error("Authentication error:", error);
return null;
}
}
})
],
// (3) session.strategy는 'jwt'가 되도록 선언
// 필요하다면 as SessionStrategy 라고 명시해줄 수도 있음
// 예) strategy: 'jwt' as SessionStrategy
session: {
strategy: 'jwt',
},
callbacks: {
// (4) 콜백에서 token, user, session 등의 타입을 좀 더 명시해주고 싶다면 아래처럼 destructuring에 제네릭/타입 지정
async jwt({ token, user }: { token: JWT; user?: User }) {
if (user) {
token.id = user.id
token.email = user.email
token.name = user.name
token.companyId = user.companyId
token.domain = user.domain
; (token as any).imageUrl = (user as any).imageUrl
}
return token
},
async session({ session, token }: { session: Session; token: JWT }) {
if (token) {
session.user = {
id: token.id as string,
email: token.email as string,
name: token.name as string,
domain: token.domain as string,
companyId: token.companyId as number,
image: (token as any).imageUrl ?? null
}
}
return session
},
},
}
const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }
|